﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System.Threading;
using System.Net;
using System.Net.Sockets;

using VDataAdapter.EventManagement;
using VDataAdapter.TestPreparation;
using VDataAdapter.WorkerManagement;
using VDataAdapter.Communication;
using VDataAdapter.BlockingQueue;
using VDataAdapter.QueueManagement;
using VDataAdapter.ClientManagement;
using VDataAdapter.PriceServerAdapter;
using log4net;

namespace VDataAdapter
{
    [TestClass]
    public class TestSubSystem
    {
        private static ILog _log;

        [TestMethod]
        public void TestSubsystemWithoutPSEventandMessageDequeuer()
        {
            string TEST_DATA = TestDataFolder.GetTestDataFolder();
            PriceServerObjectFile reader = new PriceServerObjectFile();

            Tuple<FirstTimeEvent, Queue<String>> eventMessageTuble = TestDataUtils.LoadFirstTimeEventTuple(TEST_DATA + "\\Event\\GetFirstTime.test");
            FirstTimeEvent e = eventMessageTuble.Item1;
            Queue<String> expected = eventMessageTuble.Item2;

            //Create the eQueue that will connect the price server event handler and the workers
            SimpleEventQueue eQueue = new SimpleEventQueue();

            //Start the enqueuer thread
            int expectedWaitTime = 5;
            EnqueuerThread enqueuer = new EnqueuerThread(eQueue, e, expectedWaitTime);
            Thread enQueuerThread = new Thread(enqueuer.EnQueuer);
            enQueuerThread.Start();

            CommunicationDataQueue queue = new CommunicationDataQueue(); // this is the queue worker will write to
            DataSource dataSource = new DataSource();
            VDataCommunicator comm = new VDataCommunicator(dataSource, queue);
            

            IWorkerScheduler workerScheduler = new VDataWorkerScheduler();
            WorkerManager workerManager = new WorkerManager(workerScheduler, handleWorkerException);

            workerManager.StartAllWorkers(eQueue, comm);

            workerManager.WaitPatiently();

            Queue<string> actual = TestUtils.ToStringQueue(queue);

            Assert.AreEqual(expected.Count, actual.Count);

            while (actual.Count > 0)
            {
                TimeInVariantAsserter.AreEqual(expected.Dequeue(),actual.Dequeue());
            }
        }

        private void handleWorkerException(Worker worker, Exception ex)
        {
            throw new NotImplementedException("This is not implemented, this should not has been called.");
        }

        [TestMethod(),Timeout(60000)]
        public void TestSubsystemWithoutEventQueue()
        {
            StartLog();

            string TEST_DATA = TestDataFolder.GetTestDataFolder();
            PriceServerObjectFile reader = new PriceServerObjectFile();

            Tuple<FirstTimeEvent, Queue<String>> eventFirstTimeMessageTuble = TestDataUtils.LoadFirstTimeEventTuple(TEST_DATA + "\\Event\\GetFirstTime.test");
            FirstTimeEvent e = eventFirstTimeMessageTuble.Item1;
            Queue<String> expectedFirstTimeQueue = eventFirstTimeMessageTuble.Item2;

            Tuple<TransactionsEvent[], Queue<String>> eventTransactionMessageTuble = TestDataUtils.LoadTransactionEvent(TEST_DATA + "\\EventMorning\\Transactions.test");
            TransactionsEvent[] transactionEvents = eventTransactionMessageTuble.Item1;
            Queue<String> expectedTransationsQueue = eventTransactionMessageTuble.Item2;

            while (expectedTransationsQueue.Count > 0)
            {
                expectedFirstTimeQueue.Enqueue(expectedTransationsQueue.Dequeue());
            }

            IWorkerScheduler workerScheduler = new FirstTimeMockWorkerScheduler(expectedFirstTimeQueue);

            ClientManager clientManager = new ClientManager(null, 21845, workerScheduler, 10, new DoNothingPSEventQueueEnqueuerFactory());
            clientManager.StartServingAllClients();
        }

        [TestMethod(), Timeout(200000)]
        public void TestSubsystemWithMessageOutput()
        {
            StartLog();

            string filepath = TestDataFolder.GetTestDataFolder() + "\\messageoutput.test";

            Queue<string> result = TestDataUtils.LoadMessageOutput(filepath);

            IWorkerScheduler workerScheduler = new FirstTimeMockWorkerScheduler(result);
            
            // System.Net.IPAddress ipAddress = System.Net.Dns.GetHostEntry("localhost").AddressList[0];

            ClientManager clientManager = new ClientManager(null, 21845, workerScheduler, 10, new DoNothingPSEventQueueEnqueuerFactory());
            clientManager.StartServingAllClients();
        }

        /// <summary>
        /// Test push market info into VQoutes to measure the speed of Vqoutes updating
        /// Add a debug in MarketInfoEnqueuer.StartListening in order to stop sending events while debugging
        /// this allows us ability to send market event in control
        /// </summary>
        [TestMethod(), Timeout(50000000)]
        public void TestSubSystemWithMarketInfoEvent()
        {
            StartLog();

            FirstTimeEvent firstTimeEvent = TestDataUtils.LoadFirstTimeEvent();

            TransactionsEvent[] transactionEvents = TestDataUtils.LoadTransactionEventList();

            MarketInfosEvent[] marketEvents = TestDataUtils.LoadMarketInfoEvent();

            IWorkerScheduler workerScheduler = new VDataWorkerScheduler();

            ClientManager clientManager = new ClientManager(null, 21845, workerScheduler, 10, new PSMarketEventHandlerFromFileFactory(firstTimeEvent, transactionEvents, marketEvents));
            clientManager.StartServingAllClients();
        }


        [TestMethod(), Timeout(500000)]
        public void TestSubsystemWithEventInput()
        {
            StartLog();

            FirstTimeEvent firstTimeEvent = TestDataUtils.LoadFirstTimeEvent();

            TransactionsEvent[] transactionEvents = TestDataUtils.LoadTransactionEventList();

            Top3PricesEvent[] top3PricesEvents = TestDataUtils.LoadStockTop3PricesEventList();

            IWorkerScheduler workerScheduler = new VDataWorkerScheduler();
            
           
            ClientManager clientManager = new ClientManager(null, 21845, workerScheduler, 10, new PSEventHandlerFromFileFactory(firstTimeEvent, transactionEvents, top3PricesEvents));
            clientManager.StartServingAllClients();
        }

        private static void StartLog()
        {
            log4net.Config.XmlConfigurator.Configure();
            _log = LogManager.GetLogger(typeof(TestSubSystem));
            _log.Info("Starting Log4net");
        }

        [TestMethod(), Timeout(500000)]
        public void TestSubsystemWithEventInputAndExceptionEventHandler()
        {
            StartLog();

            FirstTimeEvent firstTimeEvent = TestDataUtils.LoadFirstTimeEvent();

            TransactionsEvent[] transactionEvents = TestDataUtils.LoadTransactionEventList();

            Top3PricesEvent[] top3PricesEvents = TestDataUtils.LoadStockTop3PricesEventList();

            IWorkerScheduler workerScheduler = new VDataWorkerScheduler();


            ClientManager clientManager = new ClientManager(null, 21845, workerScheduler, 10, new PSEventHandlerFromFileWithExceptionFactory(firstTimeEvent, transactionEvents, top3PricesEvents));
            clientManager.StartServingAllClients();
        }

        /// <summary>
        /// Test pushing custom made MarketInfoEvent manually one by one.
        /// The three fields that could be customized are MarketInfo.TotalShares, MarketInfo.TotalValue and FloorCode
        /// This is to check if the Java front end is working correctly and to check system performance
        /// </summary>
        [TestMethod(), Timeout(500000)]
        public void TestSubsystemWithManualEvent()
        {
            StartLog();

            IWorkerScheduler workerScheduler = new VDataWorkerScheduler();

            ClientManager clientManager = new ClientManager(null, 21845, workerScheduler, 10, new ManualEventHandlerFactory());
            clientManager.StartServingAllClients();
        }

        /// <summary>
        /// This test load events from events.test (so that event will arrive in order recorded from PriceService as if the system is connected to PriceService)
        /// If 2 clients are connected to adapter, they are given 2 distinct sessions hence their stream of data are separated,
        /// in this case it means they received the same series of events
        /// </summary>
        [TestMethod(), Timeout(500000)]
        public void TestSubSystemCanServesMultipleJavaClientAtTheSameTime()
        {
            /// Loading event
            Event[] list = EventManager.LoadEvent(EventManagerTest.GetEventFilePath());

            var queue = new Queue<Event>();
            foreach (Event e in list)
            {
                queue.Enqueue(e);
            }

            /// Playback events
            IWorkerScheduler workerScheduler = new VDataWorkerScheduler();

            ClientManager clientManager = new ClientManager(null, 21845, workerScheduler, 10, new PSEventFromQueueFactory(queue, 10));

        }
    }
}

namespace VDataAdapter.WorkerManagement
{
    class EnqueueFromFileWorker : Worker
    {
        VDataCommunicator _comm;
        Queue<string> _stringQueueFromFile;
        private bool _stop;

        public EnqueueFromFileWorker(VDataCommunicator comm, Queue<string> stringQueueFromFile)
            : base(null, null)
        {
            _comm = comm;
            _stringQueueFromFile = stringQueueFromFile;
            _stop = false;
        }

        protected override void DoWork(object data)
        {
            int mCount = 0;
            while (_stringQueueFromFile.Count > 0)
            {
                if (_stop)
                {
                    return;
                }
                if (mCount > 7000)
                {
                    Thread.Sleep(10);
                }
                _comm.SendToQueue(_stringQueueFromFile.Dequeue());
                mCount++;
            }
        }

        override
        public void StopWork()
        {
            _stop = true;
        }
    }

    /// <summary>
    /// This class enqueue from an input string
    /// </summary>
    public class FirstTimeMockWorkerScheduler : IWorkerScheduler
    {
        private Queue<string> _queueStringFromFile;

        public FirstTimeMockWorkerScheduler(Queue<string> queueStringFromFile)
        {
            _queueStringFromFile = queueStringFromFile;
        }

        List<Worker> IWorkerScheduler.scheduleWorkers(IEventProviderQueue providerQueue, VDataCommunicator comm)
        {
            List<Worker> workers = new List<Worker>();

            Worker enqueueFromFileWorker = new EnqueueFromFileWorker(comm, _queueStringFromFile);

            workers.Add(enqueueFromFileWorker);

            return workers;
        }
    }
}

